package cn.cloud.all.security.core.userdetails;

import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import cn.cloud.all.security.core.userdetails.ReactiveUserDetailsPasswordService;
import cn.cloud.all.security.core.userdetails.ReactiveUserDetailsService;
import cn.cloud.all.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;

public class MapReactiveUserDetailsService implements ReactiveUserDetailsService, ReactiveUserDetailsPasswordService {
    private final Map<String, UserDetails> users;

    /**
     * Creates a new instance using a {@link Map} that must be non blocking.
     *
     * @param users a {@link Map} of users to use.
     */
    public MapReactiveUserDetailsService(Map<String, UserDetails> users) {
        this.users = users;
    }

    /**
     * Creates a new instance
     *
     * @param users the {@link UserDetails} to use
     */
    public MapReactiveUserDetailsService(UserDetails... users) {
        this(Arrays.asList(users));
    }

    /**
     * Creates a new instance
     *
     * @param users the {@link UserDetails} to use
     */
    public MapReactiveUserDetailsService(Collection<UserDetails> users) {
        Assert.notEmpty(users, "users cannot be null or empty");
        this.users = new ConcurrentHashMap<>();
        for (UserDetails user : users) {
            this.users.put(getKey(user.getUsername()), user);
        }
    }

    @Override
    public Mono<UserDetails> findByUsername(String username) {
        String key = getKey(username);
        UserDetails result = users.get(key);
        return result == null ? Mono.empty() : Mono.just(User.withUserDetails(result).build());
    }

    @Override
    public Mono<UserDetails> updatePassword(UserDetails user, String newPassword) {
        return Mono.just(user).map(u -> User.withUserDetails(u).password(newPassword).build())
                .doOnNext(u -> {
                    String key = getKey(user.getUsername());
                    this.users.put(key, u);
                });
    }

    private String getKey(String username) {
        return username.toLowerCase();
    }
}
